package com.bingxue.edu.management.shop.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.bingxue.edu.common.enums.CouponStatusEnum;
import com.bingxue.edu.common.enums.CouponValidTypeEnum;
import com.bingxue.edu.common.enums.DiscountTypeEnum;
import com.bingxue.edu.common.enums.ReceiveTypeEnum;
import com.bingxue.edu.framework.common.constant.CacheConstants;
import com.bingxue.edu.framework.common.model.PageQuery;
import com.bingxue.edu.framework.common.util.ValidationUtils;
import com.bingxue.edu.framework.exception.BusinessException;
import com.bingxue.edu.framework.mybatis.base.BaseService;
import com.bingxue.edu.framework.redis.RedisUtils;
import com.bingxue.edu.management.education.model.entity.Parent;
import com.bingxue.edu.management.education.service.ParentService;
import com.bingxue.edu.management.shop.mapper.CouponMapper;
import com.bingxue.edu.management.shop.model.entity.Coupon;
import com.bingxue.edu.management.shop.model.entity.CouponTemplate;
import com.bingxue.edu.management.shop.model.entity.OrderItem;
import com.bingxue.edu.management.shop.model.query.CouponQuery;
import com.bingxue.edu.management.shop.model.query.ParentCouponQuery;
import com.bingxue.edu.management.shop.model.req.CouponDistributeReq;
import com.bingxue.edu.management.shop.model.req.CouponImportReq;
import com.bingxue.edu.management.shop.model.req.CouponImportRowReq;
import com.bingxue.edu.management.shop.model.req.CouponUseReq;
import com.bingxue.edu.management.shop.model.resp.CouponDetail;
import com.bingxue.edu.management.shop.model.resp.CouponImportParseResp;
import com.bingxue.edu.management.shop.model.resp.CouponResp;
import com.bingxue.edu.parent.model.resp.ParentCouponDetailResp;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

import static com.bingxue.edu.management.education.model.entity.table.ParentTableDef.PARENT;
import static com.bingxue.edu.management.shop.model.entity.table.CouponTableDef.COUPON;
import static com.bingxue.edu.management.shop.model.entity.table.CouponTemplateTableDef.COUPON_TEMPLATE;

/**
 * 优惠卷绑定业务实现类
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class CouponService extends BaseService<CouponMapper, Coupon> {

    private final CouponTemplateService couponTemplateService;
    private final ParentService parentService;

    public Page<CouponResp> queryPage(CouponQuery query, PageQuery pageQuery) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .select(
                        COUPON.ALL_COLUMNS,
                        COUPON_TEMPLATE.NAME.as(CouponResp::getCouponTemplateName),
                        PARENT.NAME.as(CouponResp::getParentName),
                        PARENT.MOBILE_PHONE.as(CouponResp::getParentPhone)
                )
                .from(COUPON)
                .leftJoin(PARENT).on(COUPON.PARENT_ID.eq(PARENT.ID))
                .leftJoin(COUPON_TEMPLATE).on(COUPON_TEMPLATE.ID.eq(COUPON.COUPON_TEMPLATE_ID))
                .where(COUPON_TEMPLATE.NAME.like(query.getName()))
                .and(PARENT.MOBILE_PHONE.like(query.getParentPhone()))
                .and(COUPON.COUPON_STATUS.eq(query.getCouponStatus()))
                .and(COUPON.ORDER_ID.eq(query.getOrderId()))
                .orderBy(COUPON.ID, false);
        return this.getMapper().paginateWithRelationsAs(pageQuery.toPage(), queryWrapper, CouponResp.class);
    }

    public List<ParentCouponDetailResp> getParentCouponList(ParentCouponQuery query) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .select(
                        COUPON.ALL_COLUMNS,
                        COUPON_TEMPLATE.ALL_COLUMNS
                )
                .from(COUPON)
                .leftJoin(COUPON_TEMPLATE).on(COUPON_TEMPLATE.ID.eq(COUPON.COUPON_TEMPLATE_ID))
                .and(COUPON.PARENT_ID.eq(query.getParentId()))
                .and(COUPON.COUPON_STATUS.eq(query.getCouponStatus()))
                .and(COUPON.ORDER_ID.eq(query.getOrderId()));
        return this.getMapper().selectListWithRelationsByQueryAs(queryWrapper, ParentCouponDetailResp.class);
    }

    @Transactional(rollbackFor = Exception.class)
    public void distributeCoupon(CouponDistributeReq req) {
        CouponTemplate couponTemplate = couponTemplateService.getById(req.getCouponTemplateId());
        couponTemplateService.checkCouponTemplateStatus(couponTemplate);
        Integer receiveLimitCount = couponTemplate.getReceiveLimitCount();
        // 优惠券限领数量
        if (receiveLimitCount != null && receiveLimitCount > 0) {
            if(getParentCouponCount(req.getParentId(), req.getCouponTemplateId()) >= receiveLimitCount) {
                throw new BusinessException("每人仅限领取%s张".formatted(receiveLimitCount));
            }
        }
        // 优惠券发放数量
        if (couponTemplate.getReceivedCount() + 1 > couponTemplate.getDistributedCount()) {
            throw new BusinessException("优惠券已发放完毕");
        }
        // 发放
        Coupon coupon = new Coupon();
        coupon.setParentId(req.getParentId());
        coupon.setCouponTemplateId(req.getCouponTemplateId());
        coupon.setReceiveType(req.getReceiveType());
        coupon.setReceiveTime(LocalDateTime.now());
        coupon.setCouponStatus(CouponStatusEnum.UNUSED);
        this.save(coupon);

        // 领取数量 + 1
        couponTemplateService.increaseReceivedCount(req.getCouponTemplateId(), 1);

        log.info("优惠券发放成功, {}", req);
    }


    @Transactional(rollbackFor = Exception.class)
    public void deleteById(String id) {
        Coupon coupon = this.getById(id);
        ValidationUtils.throwIfNull(coupon, "优惠券不存在");
        ValidationUtils.throwIf(coupon.getCouponStatus() != CouponStatusEnum.UNUSED, "只能删除未使用的优惠券");
        couponTemplateService.increaseReceivedCount(coupon.getCouponTemplateId(), -1);
        this.getMapper().deleteById(id);
        log.info("删除优惠券成功, {}", id);
    }

    /**
     * 根据家长id和优惠券id查询优惠券信息。防止冒用
     */
    public List<CouponDetail> getParentCouponDetailByIdList(String parentId, List<String> couponIdList) {
        if (couponIdList == null || couponIdList.isEmpty()) {
            return List.of();
        }
        return this.queryChain()
                .where(COUPON.PARENT_ID.eq(parentId)).and(COUPON.ID.in(couponIdList))
                .withRelations()
                .listAs(CouponDetail.class);
    }

    /**
     * 校验优惠券是否可用
     */
    public void checkCouponValid(List<CouponDetail> couponList) {
        if (couponList.isEmpty()) {
            return;
        }
        checkCouponStackingUseValid(couponList);
        checkCouponMultipleUseValid(couponList);
        for (CouponDetail couponDetail : couponList) {
            checkCouponValid(couponDetail);
        }
    }


    /**
     * 校验优惠券状态
     */
    public void checkCouponValid(CouponDetail coupon) {
        String name = coupon.getTemplate().getName();
        ValidationUtils.throwIfNull(coupon, "优惠券[{}]不存在",name);
        ValidationUtils.throwIf(coupon.getCouponStatus() == CouponStatusEnum.USED, "优惠券[{}]已使用", name);
        ValidationUtils.throwIf(coupon.getCouponStatus() == CouponStatusEnum.OUTDATED, "优惠券[{}]已过期", name);
        checkCouponTimeValid(coupon);
    }

    /**
     * 校验叠加使用
     */
    public void checkCouponStackingUseValid(List<CouponDetail> couponList) {
        if (couponList.size() <= 1) {
            return;
        }
        for (CouponDetail coupon : couponList) {
            if (!coupon.getTemplate().getStackingUseEnabled()) {
                throw new BusinessException(String.format("优惠券[%s]无法叠加使用", coupon.getTemplate().getName()));
            }
        }
    }

    /**
     * 校验多张使用
     */
    private void checkCouponMultipleUseValid(List<CouponDetail> couponList) {
        if (couponList.size() <= 1) {
            return;
        }
        HashMap<String, Integer> couponCountMap = new HashMap<>();
        for (CouponDetail coupon : couponList) {
            if (couponCountMap.containsKey(coupon.getTemplate().getId())) {
                if (!coupon.getTemplate().getMultipleUseEnabled()) {
                    throw new BusinessException(String.format("同一订单无法使用多张[%s]优惠券", coupon.getTemplate().getName()));
                }
            } else {
                couponCountMap.put(coupon.getTemplate().getId(), 1);
            }
        }
    }

    /**
     * 校验使用门槛金额
     */
    private void checkCouponThresholdValid(CouponTemplate template, BigDecimal originalTotalPrice) {
        Double thresholdAmount = template.getThresholdAmount();
        if (thresholdAmount == null || thresholdAmount == 0.0) {
            return;
        }
        if (originalTotalPrice.compareTo(new BigDecimal(thresholdAmount)) < 0) {
            throw new BusinessException(String.format("优惠券[%s]未超过使用门槛金额", template.getName()));
        }
    }

    /**
     * 校验使用时间是否符合要求
     */
    public void checkCouponTimeValid(CouponDetail coupon) {
        LocalDateTime now = LocalDateTime.now();
        if(coupon.getTemplate().getValidType() == CouponValidTypeEnum.FIXED) {
            LocalDateTime startValidTime = coupon.getTemplate().getStartValidTime();
            if (startValidTime != null && now.isBefore(startValidTime)) {
                throw new BusinessException(String.format("优惠券[%s]未在有效期", coupon.getTemplate().getName()));
            }
            LocalDateTime endValidTime = coupon.getTemplate().getEndValidTime();
            if (endValidTime != null && now.isAfter(endValidTime)) {
                throw new BusinessException(String.format("优惠券[%s]未在有效期", coupon.getTemplate().getName()));
            }
        } else if (coupon.getTemplate().getValidType() == CouponValidTypeEnum.DAYS) {
            LocalDateTime receiveTime = coupon.getReceiveTime();
            LocalDateTime endValidTime = receiveTime.plusDays(coupon.getTemplate().getValidDays());
            if (now.isAfter(endValidTime)) {
                throw new BusinessException(String.format("优惠券[%s]未在有效期", coupon.getTemplate().getName()));
            }
        }
    }

    /**
     * 计算折扣金额
     * 计算过程会更改order item的单价/总价，计算完成后返回总的折扣金额
     * 计算过程采取向上进位保留两位小数，比如9.333元进位后实际上优惠9.34元一节课
     */
    public BigDecimal calculateDiscount(CouponTemplate template, List<OrderItem> orderItemList) {
        List<OrderItem> filteredOrderItemList = orderItemList.stream().filter(item -> {
            if (CollUtil.isNotEmpty(template.getClassIdLimit()) && !template.getClassIdLimit().contains(item.getId())) {
                return false;
            }
            if (CollUtil.isNotEmpty(template.getClassCategoryLimit()) && !template.getClassCategoryLimit().contains(item.getClassInfo().getClassCategory())) {
                return false;
            }
            if (CollUtil.isNotEmpty(template.getClassSubjectLimit()) && !template.getClassSubjectLimit().contains(item.getClassInfo().getSubject())) {
                return false;
            }
            if (CollUtil.isNotEmpty(template.getClassGradeLimit()) && !template.getClassGradeLimit().contains(item.getClassInfo().getGrade())) {
                return false;
            }
            if (CollUtil.isNotEmpty(template.getClassTermLimit()) && !template.getClassTermLimit().contains(item.getClassInfo().getTerm())) {
                return false;
            }
            if (CollUtil.isNotEmpty(template.getClassYearLimit()) && !template.getClassYearLimit().contains(item.getClassInfo().getClassYear())) {
                return false;
            }
            return true;
        }).toList();
        ValidationUtils.throwIf(filteredOrderItemList.isEmpty(), "优惠券[{}]不适用报名班级", template.getName());
        // 订单总价
        BigDecimal originalTotalPrice = new BigDecimal(0);
        int enrollLessonCount = 0;
        for (OrderItem orderItem : filteredOrderItemList) {
            BigDecimal singleClassPrice = orderItem.getActualTotalPrice();
            originalTotalPrice = originalTotalPrice.add(singleClassPrice);
            enrollLessonCount += orderItem.getLessonIndexes().size();
        }
        // 校验门槛金额
        checkCouponThresholdValid(template, originalTotalPrice);
        BigDecimal couponDiscountAmount = BigDecimal.valueOf(template.getDiscountAmount());
        BigDecimal actualDiscountAmount = BigDecimal.valueOf(0.0);
        if (template.getDiscountType() == DiscountTypeEnum.MJ) {
            actualDiscountAmount = couponDiscountAmount;
        } else if (template.getDiscountType() == DiscountTypeEnum.ZK) {
            actualDiscountAmount = originalTotalPrice.multiply(BigDecimal.valueOf(100).subtract(couponDiscountAmount)).divide(BigDecimal.valueOf(100), 2, RoundingMode.UP);
        }
        if (template.getMaxDiscountAmount() != null && template.getMaxDiscountAmount() > 0) {
            BigDecimal maxDiscountAmount = BigDecimal.valueOf(template.getMaxDiscountAmount());
            actualDiscountAmount = actualDiscountAmount.compareTo(maxDiscountAmount) > 0 ? maxDiscountAmount : actualDiscountAmount;
        }
        ValidationUtils.throwIf(originalTotalPrice.compareTo(actualDiscountAmount) < 0, "超出优惠限额");

        BigDecimal discountPerLesson = actualDiscountAmount.divide(BigDecimal.valueOf(enrollLessonCount), 2, RoundingMode.UP);
        for (OrderItem orderItem : filteredOrderItemList) {
            BigDecimal actualUnitPrice = orderItem.getActualUnitPrice().subtract(discountPerLesson);
            ValidationUtils.throwIf(actualUnitPrice.compareTo(BigDecimal.ZERO) < 0, "超出优惠限额");
            orderItem.setActualUnitPrice(actualUnitPrice);
            orderItem.setActualTotalPrice(actualUnitPrice.multiply(BigDecimal.valueOf(orderItem.getLessonIndexes().size())));
        }
        return actualDiscountAmount;
    }

    /**
     * 核销优惠券
     */
    public void useCoupon(CouponUseReq couponUseReq) {
        // 修改优惠券状态
        Coupon couponRecord = new Coupon();
        couponRecord.setId(couponUseReq.getId());
        couponRecord.setOrderId(couponUseReq.getOrderId());
        couponRecord.setCouponStatus(CouponStatusEnum.USED);
        couponRecord.setUseTime(LocalDateTime.now());
        this.updateById(couponRecord);

        // 更新优惠券模板使用数量
        couponTemplateService.increaseUsedCount(couponUseReq.getCouponTemplateId(),1);

        log.info("优惠券核销成功: {}", couponUseReq.getId());
    }


    /**
     * 撤回优惠券
     */
    @Transactional(rollbackFor = Exception.class)
    public void rollbackCoupon(CouponUseReq couponUseReq) {
        // 修改优惠券状态
        updateChain()
                .set(COUPON.COUPON_STATUS, CouponStatusEnum.UNUSED)
                .set(COUPON.ORDER_ID, null)
                .set(COUPON.USE_TIME, null)
                .where(COUPON.ID.eq(couponUseReq.getId())).update();

        // 更新优惠券模板使用数量
        couponTemplateService.increaseUsedCount(couponUseReq.getCouponTemplateId(),-1);

        log.info("优惠券撤回成功: {}", couponUseReq.getId());
    }

    /**
     * 根据家长id和订单id查询已经绑定的优惠券信息
     */
    public List<Coupon> getParentCouponByOrderId(String parentId, String orderId) {
        return this.queryChain().where(COUPON.PARENT_ID.eq(parentId))
                .and(COUPON.ORDER_ID.eq(orderId)).list();
    }

    /**
     * 查询家长拥有的指定优惠券数量
     */
    public long getParentCouponCount(String parentId,String couponTemplateId) {
        return this.queryChain().where(COUPON.PARENT_ID.eq(parentId))
                .and(COUPON.COUPON_TEMPLATE_ID.eq(couponTemplateId))
                .count();
    }

    /**
     * 下载模板文件
     * @param response
     * @throws IOException
     */
    public void downloadImportCouponTemplate(HttpServletResponse response) throws IOException {
            BufferedInputStream inputStream = FileUtil.getInputStream("templates/import/couponImportTemplate.xlsx");
            byte[] bytes = IoUtil.readBytes(inputStream);
            response.setHeader("Content-Disposition", "attachment;filename=" + URLUtil.encode("优惠劵导入模板.xlsx"));
            response.addHeader("Content-Length", String.valueOf(bytes.length));
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            response.setContentType("application/octet-stream;charset=UTF-8");
            IoUtil.write(response.getOutputStream(), true, bytes);
    }

    /**
     * 解析上传的文件
     * @param file
     * @return
     */
    public CouponImportParseResp parseImportCoupon(MultipartFile file) {
        CouponImportParseResp couponImportParseResp = new CouponImportParseResp();
        List<CouponImportRowReq> couponImportRowReqs;
        // 读取表格数据
        try {
            couponImportRowReqs = EasyExcel.read(file.getInputStream())
                    .head(CouponImportRowReq.class)
                    .sheet()
                    .headRowNumber(1)
                    .doReadSync();
        } catch (Exception e) {
            log.error("用户导入数据文件解析异常：", e);
            throw new BusinessException("数据文件解析异常");
        }

        couponImportParseResp.setTotalRows(couponImportRowReqs.size());
        ValidationUtils.throwIf(CollUtil.isEmpty(couponImportRowReqs),"数据文件格式错误");

        //转换为实体对象
        List<Coupon> importCoupons = transformReqAndValidate(couponImportRowReqs,couponImportParseResp);

        // 设置导入会话并缓存数据，有效期10分钟
        String importKey = UUID.fastUUID().toString(true);
        RedisUtils.set(CacheConstants.DATA_IMPORT_KEY + importKey, JSONUtil.toJsonStr(importCoupons), Duration
                .ofMinutes(10));
        couponImportParseResp.setImportKey(importKey);

        return couponImportParseResp;
    }

    /**
     * 过滤错误的信息
     * @param couponImportRowReqs
     * @return
     */
    private List<CouponImportRowReq> filterErrorCouponImportData(List<CouponImportRowReq> couponImportRowReqs) {
        List<CouponImportRowReq> filteredList = couponImportRowReqs.stream()
                .filter(obj ->  obj.getCouponTemplateId() != null && obj.getParentPhone() != null)
                .collect(Collectors.toList());
        return filteredList;
    }

    /**
     * 导入优惠卷
     * @param req
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Void importCoupon(CouponImportReq req) {
        // 校验导入会话是否过期
        List<Coupon> importCoupons;
        try {
            String data = RedisUtils.get(CacheConstants.DATA_IMPORT_KEY + req.getImportKey());
            importCoupons = JSONUtil.toList(data, Coupon.class);
            ValidationUtils.throwIf(CollUtil.isEmpty(importCoupons), "导入已过期，请重新上传");
        } catch (Exception e) {
            log.error("导入异常:", e);
            throw new BusinessException("导入已过期，请重新上传");
        }

        for(Coupon coupon:importCoupons){
            this.save(coupon);
            // 领取数量 + 1
            couponTemplateService.increaseReceivedCount(coupon.getCouponTemplateId(), 1);
        }

        log.info("优惠券发放成功, {}", req);
        return null;
    }


    /**
     * 将行数据转换为实体并且做校验(行数据是否包含空字段,该用户优惠卷是否已达领取上限,优惠卷是否发放完毕
     * @param importRowReqs  导入行数据
     * @param couponImportParseResp 请求返回数据
     * @return 优惠卷实体数据
     */
    private List<Coupon> transformReqAndValidate(List<CouponImportRowReq> importRowReqs,CouponImportParseResp couponImportParseResp) {
        // 做数据格式校验: 过滤包含空字段的行无效数据
        List<CouponImportRowReq> validCouponImportRowReqs = filterErrorCouponImportData(importRowReqs);
        if (CollUtil.isEmpty(validCouponImportRowReqs)) {
            throw new BusinessException("数据文件格式错误");
        }

        //做优惠卷校验
        List<Coupon> coupons = new ArrayList<>();
        for (CouponImportRowReq couponImportRowReq :validCouponImportRowReqs){
            CouponTemplate couponTemplate = couponTemplateService.getById(couponImportRowReq.getCouponTemplateId());
            couponTemplateService.checkCouponTemplateStatus(couponTemplate);
            Integer receiveLimitCount = couponTemplate.getReceiveLimitCount();

            Parent parent = parentService.getByPhone(couponImportRowReq.getParentPhone());
            if(parent == null) {
                log.warn("手机号[{}]不存在", couponImportRowReq.getParentPhone());
                continue;
            }
            // 优惠券限领数量
            if (receiveLimitCount != null && receiveLimitCount > 0) {
                if(getParentCouponCount(parent.getId(), couponImportRowReq.getCouponTemplateId()) >= receiveLimitCount) {
                    log.warn("优惠券领取数量已达上限");
                    continue;
                }
            }
            // 优惠券发放数量
            if (couponTemplate.getReceivedCount() + 1 > couponTemplate.getDistributedCount()) {
                log.warn("优惠券已发放完毕");
                continue;
            }

            Coupon coupon = new Coupon();
            coupon.setParentId(parent.getId());
            coupon.setCouponTemplateId(couponImportRowReq.getCouponTemplateId());
            coupon.setReceiveType(ReceiveTypeEnum.BACKEND);
            coupon.setReceiveTime(LocalDateTime.now());
            coupon.setCouponStatus(CouponStatusEnum.UNUSED);
            coupons.add(coupon);
        }
        //设置有效行数
        couponImportParseResp.setValidRows(coupons.size());
        return coupons;
    }

    /**
     * 撤销优惠卷
     * @param couponId 优惠卷id
     */
    @Transactional(rollbackFor = Exception.class)
    public void cancelCoupon(String couponId) {
        //判断该优惠卷是否已使用
        ValidationUtils.throwIf(checkCouponUsedValid(couponId),"该优惠卷已被使用,无法撤销");

        //根据优惠卷id查询优惠卷模板id
        String couponTemplateId = this.getCouponTemplateIdByCouponId(couponId);

        //优惠卷模板发放数量-1
        couponTemplateService.decreaseDistributedCount(couponTemplateId);

        //删除优惠卷记录
        this.deleteById(couponId);
    }

    /**
     * 检查该优惠卷是否已使用 :已使用返回true ,未使用返回false
     * @param couponId 优惠卷id
     */
    private boolean checkCouponUsedValid(String couponId) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .select(COUPON.ALL_COLUMNS)
                .from(COUPON)
                .where(COUPON.ID.eq(couponId))
                .and(COUPON.COUPON_STATUS.eq(CouponStatusEnum.USED));
        return this.exists(queryWrapper);
    }

    /**
     * 根据优惠卷id查询优惠卷模板id
     * @param couponId 优惠卷id
     */
    private String getCouponTemplateIdByCouponId(String couponId) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .select(COUPON.COUPON_TEMPLATE_ID)
                .from(COUPON)
                .where(COUPON.ID.eq(couponId));

        return this.getOneAs(queryWrapper,String.class);
    }
}
